// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 此文件实现各种字段和方法查找功能。

package types

import "go/token"

// LookupFieldOrMethod查找具有给定包和名称的字段或方法
// 并返回相应的*Var或*Func、索引序列和
// bool，指示在指向
// 字段或方法。如果设置了可寻址，则T是可寻址的类型
// 变量（仅与方法查找有关）。
// None
// 最后一个索引项是中的字段或方法索引（可能嵌入）
// 键入找到条目的位置，或者：
// None
// 1） 命名类型的已声明方法的列表；或
// 2） 接口类型的所有方法（方法集）的列表；或
// 3） 结构类型的字段列表。
// None
// 前面的索引项是嵌入式结构字段的索引
// 遍历以到达找到的条目，从深度0开始。
// None
// 如果没有找到条目，则返回nil对象。在本例中，返回了
// 索引值和间接值具有以下含义：
// None
// -如果索引！=nil，则索引序列指向一个不明确的条目
// （同一名称在同一嵌入级别多次出现）。
// None
// -如果设置了间接，则找到了指针接收器类型的方法
// 但是在从实际接收器类型到的路径上没有指针
// 方法的正式接收方基类型，接收方也不可寻址。
// None
func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) {
	return (*Checker)(nil).lookupFieldOrMethod(T, addressable, pkg, name)
}

// Checker.lookupFieldOrMethod的内部使用：如果obj结果是一个方法
// 方法的签名与具体（非接口）类型关联
// 可能未完全设置。访问前调用Checker.objDecl（obj，nil）
// 方法的类型。
// TODO（gri）现在我们提供了*检查器，我们可能可以删除它
// 通过从lookupFieldOrMethod调用Checker.objDecl进行警告。侦查

// lookupFieldOrMethod与外部版本类似，但完成了接口
// 必要时。
func (check *Checker) lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) {
	// 方法不能与命名指针类型关联
	// （规格：“T表示的类型称为接收器底座类型；
	// 它不能是指针或接口类型，必须声明
	// 在与方法“”相同的包中）。
	// 因此，如果我们有一个命名的指针类型，则继续下面的操作
	// 指针类型，但如果是方法，则放弃结果，因为
	// 还没有找到T（另见第8590期）。
	if t := asNamed(T); t != nil {
		if p, _ := t.underlying.(*Pointer); p != nil {
			obj, index, indirect = check.rawLookupFieldOrMethod(p, false, pkg, name)
			if _, ok := obj.(*Func); ok {
				return nil, nil, false
			}
			return
		}
	}

	return check.rawLookupFieldOrMethod(T, addressable, pkg, name)
}

// TODO（gri）下面的命名类型合并和seen映射必须是
// 由给定类型的唯一键索引。验证指定的
// 类型始终只有一个表示（即使在导入时也是如此）
// 间接通过不同的软件包。）

// rawLookupFieldOrMethod只能由lookupFieldOrMethod和missingMethod调用。
func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) {
	// 警告：此函数中的代码非常微妙-不要随意修改！
	// 此函数和NewMethodSet应保持同步。

	if name == "_" {
		return // 从未找到空白字段/方法
	}

	typ, isPtr := deref(T)

	// *typ，其中typ是一个接口，没有方法。
	// 小心：典型值可能为零（第39634期，崩溃3）。
	if typ == nil || isPtr && IsInterface(typ) {
		return
	}

	// 从最浅深度的单一入口开始。
	current := []embeddedType{{typ, nil, isPtr, false}}

	// 我们已经看到的命名类型是惰性分配的。
	// 用于避免递归类型情况下无休止的搜索。
	// 由于递归类型只能使用命名类型，因此
	// 只需要追踪那些。
	// （如果我们允许类型别名构造递归类型，
	// 我们必须使用类型标识，而不是指针相等
	// 地图键比较，正如我们在consolidateMultiples中所做的。）
	var seen map[*Named]bool

	// 搜索当前深度
	for len(current) > 0 {
		var next []embeddedType // 在当前深度找到嵌入类型

		// 在当前深度的所有类型中查找（pkg，name）
		var tpar *_TypeParam // 如果obj接收器是类型参数，则设置
		for _, e := range current {
			typ := e.typ

			// 如果我们有一个命名类型，我们可能有关联的方法。
			// 先找那些。
			if named := asNamed(typ); named != nil {
				if seen[named] {
					// 我们以前在较浅的深度见过这种类型
					// （请注意，当前深度处该类型的倍数
					// 以前合并过）。该深度处的类型将显示阴影
					// 这是当前深度的同一类型，因此我们可以忽略
					// 这个。
					continue
				}
				if seen == nil {
					seen = make(map[*Named]bool)
				}
				seen[named] = true

				// 查找匹配的附加方法
				if i, m := lookupMethod(named.methods, pkg, name); m != nil {
					// 潜在匹配
					// 注意：方法可能还没有正确的签名
					index = concat(e.index, i)
					if obj != nil || e.multiples {
						return nil, index, false // 碰撞
					}
					obj = m
					indirect = e.indirect
					continue // 我们不能有匹配的字段或接口方法
				}

				// 继续使用基础类型，但前提是它不是类型参数
				// TODO（gri）这就是我们要对类型参数执行的操作吗？（规格问题）
				// TODO（#45639）由于跳过
				// 应改进基础类型参数。
				typ = named.under()
				if asTypeParam(typ) != nil {
					continue
				}
			}

			tpar = nil
			switch t := typ.(type) {
			case *Struct:
				// 查找匹配字段并收集嵌入类型
				for i, f := range t.fields {
					if f.sameId(pkg, name) {
						assert(f.typ != nil)
						index = concat(e.index, i)
						if obj != nil || e.multiples {
							return nil, index, false // 碰撞
						}
						obj = f
						indirect = e.indirect
						continue // 我们不能有匹配的接口方法
					}
					// 收集嵌入式结构字段以搜索下一个
					// 深度较低，但前提是我们还没有看到比赛
					// （如果我们有匹配项，则为所需字段或
					// 我们在同一深度上有一个名称冲突；在任何一种情况下
					// 案例（我们不需要进一步研究）。
					// 嵌入字段的格式始终为T或*T，其中
					// T是一个类型名。如果e.typ在同一时间出现多次
					// 该深度f.typ在下一次显示多次
					// 深度
					if obj == nil && f.embedded {
						typ, isPtr := deref(f.typ)
						// TODO（gri）优化：忽略无法
						// 具有字段或方法（仅命名、结构和
						// 需要考虑接口类型）。
						next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples})
					}
				}

			case *Interface:
				// 寻找匹配的方法
				// TODO（gri）t.allMethods已排序-使用二进制搜索
				check.completeInterface(token.NoPos, t)
				if i, m := lookupMethod(t.allMethods, pkg, name); m != nil {
					assert(m.typ != nil)
					index = concat(e.index, i)
					if obj != nil || e.multiples {
						return nil, index, false // 碰撞
					}
					obj = m
					indirect = e.indirect
				}

			case *_TypeParam:
				// 只考虑类型参数绑定中的显式方法，而不是
				// 对类型列表中的所有类型都通用的方法。
				if i, m := lookupMethod(t.Bound().allMethods, pkg, name); m != nil {
					assert(m.typ != nil)
					index = concat(e.index, i)
					if obj != nil || e.multiples {
						return nil, index, false // 碰撞
					}
					tpar = t
					obj = m
					indirect = e.indirect
				}
			}
		}

		if obj != nil {
			// 找到了一个潜在的匹配项
			// 说明：“如果方法集（类型）为x，则方法调用x.m（）有效
			// 包含m，参数列表可以指定给参数
			// m。如果x是可寻址的，并且&x的方法集包含m，则x.m（）
			// 是（&x）.m（）的缩写。
			if f, _ := obj.(*Func); f != nil {
				// 确定方法是否有指针接收器
				hasPtrRecv := tpar == nil && ptrRecv(f)
				if hasPtrRecv && !indirect && !addressable {
					return nil, nil, true // 需要指针/可寻址接收器
				}
			}
			return
		}

		current = check.consolidateMultiples(next)
	}

	return nil, nil, false // 找不到
}

// embeddedType表示嵌入式类型
type embeddedType struct {
	typ       Type
	index     []int // 嵌入字段索引，从深度0处的索引开始
	indirect  bool  // 如果设置，则指向此字段的路径上有一个间接指针
	multiples bool  // 如果设置，则“类型”在此深度显示多次
}

// consolidateMultiples收集具有相同类型的多个列表项
// 输入一个标记为包含多个的条目。结果是
// 综合清单。
func (check *Checker) consolidateMultiples(list []embeddedType) []embeddedType {
	if len(list) <= 1 {
		return list // 最多一个条目-无需执行任何操作
	}

	n := 0                     // 具有唯一类型的条目数
	prev := make(map[Type]int) // 以前看到的类型的索引
	for _, e := range list {
		if i, found := check.lookupType(prev, e.typ); found {
			list[i].multiples = true
			// 忽略此条目
		} else {
			prev[e.typ] = n
			list[n] = e
			n++
		}
	}
	return list[:n]
}

func (check *Checker) lookupType(m map[Type]int, typ Type) (int, bool) {
	// 快速路径：可能类型相同
	if i, found := m[typ]; found {
		return i, true
	}

	for t, i := range m {
		if check.identical(t, typ) {
			return i, true
		}
	}

	return 0, false
}

// 如果V实现了T，则MissingMethod返回（nil，false），否则
// 返回T所需的缺少的方法以及该方法是否缺少
// 只是型号不对。
// None
// 对于非接口类型V，或者如果设置了static，则V在所有情况下实现T
// T的方法出现在V中。否则（V是一个接口和静态
// 未设置），MissingMethod仅检查T的方法
// 在V中存在匹配类型（例如，对于类型断言x.（T），其中
// x为接口类型V）。
// None
func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool) {
	m, typ := (*Checker)(nil).missingMethod(V, T, static)
	return m, typ != nil
}

// missingMethod类似于missingMethod，但接受*检查器作为
// 接收器和可寻址标志。
// 如果通过调用missingMethod，则接收器可能为零
// 导出的API调用（如MissingMethod），即当所有
// 方法已经过类型检查。
// 如果类型具有正确命名的方法，但具有错误的
// 签名时，也将返回现有方法。
// 要改进错误消息，请同时报告错误的签名
// 当方法存在于*V而不是V上时。
func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, wrongType *Func) {
	check.completeInterface(token.NoPos, T)

	// 常见情况下的快速路径
	if T.Empty() {
		return
	}

	if ityp := asInterface(V); ityp != nil {
		check.completeInterface(token.NoPos, ityp)
		// TODO（gri）所有方法都已排序-可以更有效地执行此操作
		for _, m := range T.allMethods {
			_, f := lookupMethod(ityp.allMethods, m.pkg, m.name)

			if f == nil {
				// 如果m是神奇的方法==我们还可以（接口是可比较的）
				if m.name == "==" || !static {
					continue
				}
				return m, f
			}

			ftyp := f.typ.(*Signature)
			mtyp := m.typ.(*Signature)
			if len(ftyp.tparams) != len(mtyp.tparams) {
				return m, f
			}

			// 如果方法有类型参数，我们不关心它们是否
			// 是否相同，只要它们匹配。使用统一
			// 看看他们是否能匹配。
			// TODO（gri）这总是正确的吗？那么类型边界呢？
			// （另一种方法是重命名/subst类型参数并进行比较。）
			u := newUnifier(check, true)
			u.x.init(ftyp.tparams)
			if !u.unify(ftyp, mtyp) {
				return m, f
			}
		}

		return
	}

	// 如果具体类型实现了T的所有方法，则它实现了T。
	Vd, _ := deref(V)
	Vn := asNamed(Vd)
	for _, m := range T.allMethods {
		// TODO（gri）是否应该改为调用lookupFieldOrMethod（为什么不）？
		obj, _, _ := check.rawLookupFieldOrMethod(V, false, m.pkg, m.name)

		// 检查*V是否实现了T的这个方法。
		if obj == nil {
			ptr := NewPointer(V)
			obj, _, _ = check.rawLookupFieldOrMethod(ptr, false, m.pkg, m.name)
			if obj != nil {
				return m, obj.(*Func)
			}
		}

		// 我们必须有一个方法（不是匹配函数类型的字段）
		f, _ := obj.(*Func)
		if f == nil {
			// 如果m是神奇的方法==并且V是可比较的，我们就可以了
			if m.name == "==" && Comparable(V) {
				continue
			}
			return m, nil
		}

		// 方法可能尚未完全设置签名
		if check != nil {
			check.objDecl(f, nil)
		}

		// 两个方法必须具有相同数量的类型参数
		ftyp := f.typ.(*Signature)
		mtyp := m.typ.(*Signature)
		if len(ftyp.tparams) != len(mtyp.tparams) {
			return m, f
		}

		// 如果V是（实例化的）泛型类型，则其方法仍然是
		// 使用原始（声明）接收器类型参数化
		// 参数（subst）只是复制现有的方法列表，它
		// 不实例化方法）。
		// 为了比较签名，替换接收器
		// 具有V的实例化类型参数的ftyp的类型参数。
		// 这会延迟地实例化方法f的签名。
		if Vn != nil && len(Vn.tparams) > 0 {
			// 小心：类型参数的数量可能不匹配
			// 接收器参数的数量。如果是这样，则会出现错误
			// 之前已报告，但长度差异仍然存在
			// 在这里在这种情况下尽早退出以防止断言
			// makeSubsmap中失败。
			// TODO（gri）我们可以通过固定长度来避免这种检查吗？
			if len(ftyp.rparams) != len(Vn.targs) {
				return
			}
			ftyp = check.subst(token.NoPos, ftyp, makeSubstMap(ftyp.rparams, Vn.targs)).(*Signature)
		}

		// 如果方法有类型参数，我们不关心它们是否
		// 是否相同，只要它们匹配。使用统一
		// 看看他们是否能匹配。
		// TODO（gri）这总是正确的吗？那么类型边界呢？
		// （另一种方法是重命名/subst类型参数并进行比较。）
		u := newUnifier(check, true)
		u.x.init(ftyp.tparams)
		if !u.unify(ftyp, mtyp) {
			return m, f
		}
	}

	return
}

// assertableTo报告类型V的值是否可以断言为类型T。
// 它返回（nil，false）作为肯定回答。否则，它将返回一个缺少的
// V所需的方法，以及它是否缺少或只是具有错误的类型。
// 如果通过导出的API调用调用assertableTo，则接收方可能为nil
// （如AssertableTo），即当所有方法都已进行类型检查时。
// 如果设置了全局常量forceStrict，则已知的断言将失败
// 是不允许的。
func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) {
	// 如果T是接口，则不需要进行静态检查
	// 规范：“如果T是接口类型，则x.（T）断言
	// 动态类型的x实现接口T。“
	if asInterface(T) != nil && !forceStrict {
		return
	}
	return check.missingMethod(T, V, false)
}

// 如果它是*指针并返回其基和true，则deref dereferences typ。
// 否则它将返回（typ，false）。
func deref(typ Type) (Type, bool) {
	if p, _ := typ.(*Pointer); p != nil {
		return p.base, true
	}
	return typ, false
}

// derefStructPtr如果是指向某个对象的（命名或未命名）指针，则为dereferences typ
// （命名或未命名）结构并返回其基。否则返回typ。
func derefStructPtr(typ Type) Type {
	if p := asPointer(typ); p != nil {
		if asStruct(p.base) != nil {
			return p.base
		}
	}
	return typ
}

// concat返回连接列表和i的结果。
// 结果不与列表共享其基础数组。
func concat(list []int, i int) []int {
	var t []int
	t = append(t, list...)
	return append(t, i)
}

// fieldIndex返回具有匹配包和名称的字段的索引，或值<0。
func fieldIndex(fields []*Var, pkg *Package, name string) int {
	if name != "_" {
		for i, f := range fields {
			if f.sameId(pkg, name) {
				return i
			}
		}
	}
	return -1
}

// lookupMethod返回具有匹配包和名称的and方法的索引，或（-1，nil）。
func lookupMethod(methods []*Func, pkg *Package, name string) (int, *Func) {
	if name != "_" {
		for i, m := range methods {
			if m.sameId(pkg, name) {
				return i, m
			}
		}
	}
	return -1, nil
}
